package request

import (
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"io/ioutil"

	"gitee.com/zjlsliupei/ghelp"
	"github.com/beego/beego/v2/client/httplib"
	"github.com/tidwall/gjson"
)

func NewRequest(addr string) Request {
	return Request{
		addr:          addr,
		beforeExecCbs: BeforeExecCbs,
		afterExecCbs:  AfterExecCbs,
	}
}

type Request struct {
	addr          string
	rpcName       string
	funcName      string
	beforeExecCbs []BeforeExecCb
	afterExecCbs  []AfterExecCb
	meta          ghelp.MAP
	ginContext    *gin.Context
}

//RpcName 设备RpcName
func (this *Request) RpcName(rpcName string) {
	this.rpcName = rpcName
}

// FuncName 设置FuncName名称
func (this *Request) FuncName(funcName string) {
	this.funcName = funcName
}

// AutoMeta 自动设置meta，根据gin的上下文来解析，meta; 如果同时使用了Meta优先以Meta为准
func (this *Request) AutoMeta(c *gin.Context) {
	this.ginContext = c
}

// SetMeta 设置meta
func (this *Request) SetMeta(meta ghelp.MAP) {
	this.meta = meta
}

// Do 发送http请求
func (this *Request) Do(arg interface{}) (gjson.Result, error) {
	req := httplib.Post(this.addr + "/" + this.rpcName + "/" + this.funcName)
	req.Header("X-Rpcx-SerializeType", "1")
	req.Header("X-Rpcx-ServicePath", this.rpcName)
	req.Header("X-Rpcx-ServiceMethod", this.funcName)
	body := ghelp.MAP{
		"arg": arg,
	}
	// 设置meta, 优先使用用户自己设置的meta
	if this.meta == nil && this.ginContext != nil {
		rawBody, err := this.getBody()
		if err != nil {
			ghelp.JsonDecodeTo(gjson.GetBytes(rawBody, "meta").String(), &this.meta)
		}
	}
	if this.meta != nil {
		body["meta"] = this.meta
	}
	req.JSONBody(body)
	//执行前置hook
	this.execBeforeHook(arg)
	res, err := req.Response()
	if err != nil {
		//执行后置hook
		go this.execAfterHook(arg, gjson.Result{}, err)
		return gjson.Result{}, err
	}
	defer res.Body.Close()
	bodyByte, _ := ioutil.ReadAll(res.Body)
	_res := gjson.Parse(string(bodyByte))
	resReturn := _res.Get("result")
	//执行后置hook
	go this.execAfterHook(arg, resReturn, err)

	if res.StatusCode != 200 {
		if rpcxErrMsg := res.Header.Get("X-Rpcx-Errormessage"); rpcxErrMsg != "" {
			return resReturn, errors.New(rpcxErrMsg)
		} else {
			return resReturn, fmt.Errorf("http status is %d", res.StatusCode)
		}
	}
	if !_res.Get("status").Exists() {
		return resReturn, errors.New("格式不正确")
	}
	if _res.Get("status").Int() != 0 {
		return resReturn, errors.New(_res.Get("errmsg").String())
	}
	if _res.Get("result.status").Int() != 200 {
		var message string
		if _res.Get("result.message").Exists() {
			message = _res.Get("result.message").String()
		} else {
			message = fmt.Sprintf("result.status is %s", _res.Get("result.status").String())
		}
		return resReturn, errors.New(message)
	}
	return resReturn, nil
}

// getBody 获取请求body，全局当前上下文中会保存
func (this *Request) getBody() ([]byte, error) {
	if this.ginContext == nil {
		return []byte{}, errors.New("ginContext is nil")
	}
	var err error
	globalBody, exist := this.ginContext.Get("globalBody")
	if !exist {
		globalBody, err = this.ginContext.GetRawData()
		this.ginContext.Set("globalBody", globalBody)
	}
	return globalBody.([]byte), err
}

// execBeforeHook 执行前置hook
func (this *Request) execBeforeHook(arg interface{}) {
	if len(this.beforeExecCbs) > 0 {
		for _, cb := range this.beforeExecCbs {
			cb(BeforeExec{
				Addr:     this.addr,
				RpcName:  this.rpcName,
				FuncName: this.funcName,
				Args:     arg,
			})
		}
	}
}

// execAfterHook 执行后置hook
func (this *Request) execAfterHook(arg interface{}, response gjson.Result, err error) {
	if len(this.afterExecCbs) > 0 {
		for _, cb := range this.afterExecCbs {
			cb(AfterExec{
				Addr:          this.addr,
				RpcName:       this.rpcName,
				FuncName:      this.funcName,
				Args:          arg,
				Response:      response,
				ResponseError: err,
			})
		}
	}
}
